#include "stdafx.h"
#include "stdio.h"
#include "resource1.h"
#include <windows.h>
#include <objbase.h>
#include <olectl.h>
#include <shellapi.h>
#include <shlwapi.h>

TCHAR gstr[500];

IPicture* pPicture = NULL;
PROCESS_INFORMATION p_i;
HWND hdlg = NULL;


// SUPPORTING FUNCTIONS

// Returns false when it's time to quit.
bool PumpMessages()
{
	// pump messages
	MSG msg;
	while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) != FALSE)
	{
	  // check for WM_QUIT
	  if (msg.message == WM_QUIT)
	  {
		return false;
	  }

	  // dispatch thread message
	  TranslateMessage(&msg);
	  DispatchMessage(&msg);
	}

	return true;
}

// Returns false when it's time to quit.
bool PumpMessagesAndWaitForInputIdle(HANDLE hProcess)
{
	while (WaitForInputIdle(hProcess, 10) == WAIT_TIMEOUT)
	{
	  if (!PumpMessages())
	  {
		return false;
	  }
	}
	return true;
}

bool FoundWindowHandle;

BOOL CALLBACK FindMainWindow(HWND hWnd, LPARAM lParam)
{
	DWORD DesiredProcessId = (DWORD)lParam;
	DWORD testProcessId = NULL;
	if (0 < GetWindowThreadProcessId(hWnd, (LPDWORD)(&testProcessId)))
	{
	  if (testProcessId == DesiredProcessId)
	  {
		if (0 != IsWindowVisible(hWnd))
		{
		  FoundWindowHandle = true;
		  return FALSE; // Quit looking.  Found it!
		}
	  }
	}

	// Keep looking.
	return TRUE;
}

// Returns false when the app should quit.
bool WaitForMainWindow(HANDLE hProcess, DWORD processId)
{
	while (!FoundWindowHandle)
	{
	  DWORD exitCode = 0;
	  if (0 != GetExitCodeProcess(hProcess, &exitCode))
	  {
		if (exitCode != STILL_ACTIVE)
		{
		  return false; // We'll never find the main window, the process no longer exists.
		}
	  }

	  if (!PumpMessages())
	  {
		return false; // Time to quit.
	  }
	  Sleep(100);

	  // Look for the main window for the process we want.
	  FoundWindowHandle = false;
	  EnumDesktopWindows(NULL, FindMainWindow, processId);
	  if (FoundWindowHandle)
	  {
		return true;
	  }
	}

	return true;
}
  
HFONT QuickCreateFont(HDC dc, int height, int weight, TCHAR *szFace, bool underline = false)
{
HFONT hfnt = NULL;
hfnt = CreateFont(-MulDiv(height, GetDeviceCaps(dc, LOGPIXELSY), 72), 0, 0, 0, weight, FALSE, (DWORD)underline, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, szFace);
return hfnt;
}

HRESULT LastError () {
   HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
   if (SUCCEEDED(hr)) 
      return E_FAIL;
   return hr;
}

void MyCopyMemory(void* dest, void* src, int size)
{
	unsigned char *pdest, *psrc;
	pdest = (unsigned char*)dest;
	psrc = (unsigned char*)src;

	// Loop over the count in blocks of 4 bytes, copying an 
	// integer (4 bytes) at a time: 
	for (int n = 0; n < size/4; n++) 
	{ 
		*((int*)pdest) = *((int*)psrc); 
		pdest += 4; 
		psrc += 4; 
	} 

	// Complete the copy by moving any bytes that weren't 
	// moved in blocks of 4: 
	for (int n = 0; n < size%4; n++) 
	{ 
		*pdest = *psrc; 
		pdest++; 
		psrc++; 
	} 
}

// This function gets the file version info structure
HRESULT GetFileVersion (char *filename, VS_FIXEDFILEINFO *pvsf)
{
   DWORD dwHandle;
   DWORD cchver = GetFileVersionInfoSize(filename,&dwHandle);
   if (cchver == 0) 
      return LastError();
   char* pver = new char[cchver];
   BOOL bret = GetFileVersionInfo(filename,dwHandle,cchver,pver);
   if (!bret) 
      return LastError();
   UINT uLen;
   void *pbuf;
   bret = VerQueryValue(pver,"\\",&pbuf,&uLen);
   if (!bret) 
      return LastError();
   MyCopyMemory(pvsf,pbuf,sizeof(VS_FIXEDFILEINFO));
   delete[] pver;
   return S_OK;
}

#define HIMETRIC_INCH   2540    // HIMETRIC units per inch

void Paint(HDC dc)
{
	OLE_XSIZE_HIMETRIC width;
	OLE_YSIZE_HIMETRIC height;
	pPicture->get_Width( &width );
	pPicture->get_Height( &height );
    //Convert to device units
    int width2  = MulDiv(width,  GetDeviceCaps(dc, LOGPIXELSX), HIMETRIC_INCH);
    int height2 = MulDiv(height, GetDeviceCaps(dc, LOGPIXELSY), HIMETRIC_INCH);
	RECT rc;
	rc.top = 0;
	rc.bottom = width2;
	rc.left = 0;
	rc.right = height2;
	pPicture->Render(dc, 0, 0, width2, height2, 0, height, width, -height, &rc);
	if(GetPrivateProfileInt("SplashInfo", "WhiteOnBlack", 0, ".\\Loader.ini"))
		SetTextColor(dc, RGB(255,255,255));
	HFONT fnt;
	HFONT old;
	if(GetPrivateProfileInt("SplashInfo", "ShowVersion", 1, ".\\Loader.ini"))
	{
		VS_FIXEDFILEINFO vsf;
		SecureZeroMemory(&vsf, sizeof(vsf));
		GetPrivateProfileString("General", "Execute", "Main.exe", gstr, 260, ".\\Loader.ini");
		if(GetFileVersion(gstr, &vsf) == 0)
		{
			GetPrivateProfileString("SplashInfo", "VersionFontFace", "Verdana", gstr, 260, ".\\Loader.ini");
			UINT size = GetPrivateProfileInt("SplashInfo", "VersionFontSize", 10, ".\\Loader.ini");
			BOOL bold = (BOOL)GetPrivateProfileInt("SplashInfo", "VersionFontBold", 1, ".\\Loader.ini");
			SetBkMode(dc, TRANSPARENT);
			fnt = QuickCreateFont(dc, size, bold ? FW_BOLD : FW_NORMAL, gstr);
			old = (HFONT)SelectObject(dc, fnt);
			wsprintf(gstr, "Version: %d.%d.%d.%d", HIWORD(vsf.dwFileVersionMS), LOWORD(vsf.dwFileVersionMS), HIWORD(vsf.dwFileVersionLS), LOWORD(vsf.dwFileVersionLS));
			TextOut(dc, GetPrivateProfileInt("SplashInfo", "VersionX", 268, ".\\Loader.ini"), GetPrivateProfileInt("SplashInfo", "VersionY", 118, ".\\Loader.ini"), gstr, lstrlen(gstr));
			SelectObject(dc, old);
			DeleteObject(fnt);
		}
	}
	if(GetPrivateProfileInt("SplashInfo", "Show", 1, ".\\Loader.ini"))
	{
		GetPrivateProfileString("SplashInfo", "FontFace", "Verdana", gstr, 260, ".\\Loader.ini");
		UINT size = GetPrivateProfileInt("SplashInfo", "FontSize", 8, ".\\Loader.ini");
		BOOL bold = (BOOL)GetPrivateProfileInt("SplashInfo", "FontBold", 1, ".\\Loader.ini");
		fnt = QuickCreateFont(dc, size, bold ? FW_BOLD : FW_NORMAL, gstr);
		old = (HFONT)SelectObject(dc, fnt);
		GetPrivateProfileString("SplashInfo", "Text", "WARNING: This product is\nprotected by copyright law\nand international treaties.\nUnauthorized reproduction or\ndistribution of this software\nwill result in severe civil and\ncriminal penalties.\nDacris Software assumes no\nliability for any damages that\nmay be incurred through the\nuse of this program.", gstr, 800, ".\\Loader.ini");
		rc.bottom = GetPrivateProfileInt("SplashInfo", "Bottom", 312, ".\\Loader.ini");
		rc.left = GetPrivateProfileInt("SplashInfo", "Left", 268, ".\\Loader.ini");
		rc.right = GetPrivateProfileInt("SplashInfo", "Right", 460, ".\\Loader.ini");
		rc.top = GetPrivateProfileInt("SplashInfo", "Top", 160, ".\\Loader.ini");
		DrawText(dc, gstr, (int)lstrlen(gstr), &rc, DT_WORDBREAK);
		SelectObject(dc, old);
		DeleteObject(fnt);
	}
}

LRESULT CALLBACK MainWndProc( HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam )
{
    // which message are we handling?
    switch( Message )
    {
		case WM_ERASEBKGND:
		{
			PAINTSTRUCT ps;
			HDC hdc = BeginPaint(hWnd, &ps);
			Paint(hdc);
			EndPaint(hWnd, &ps);
			return 1;
		}
		case WM_PAINT:
		{
			PAINTSTRUCT ps;
			BeginPaint(hWnd, &ps);
			Paint(ps.hdc);
			EndPaint(hWnd, &ps);
			break;
		}
		case WM_PRINTCLIENT:
		{
			HDC hdc = (HDC)wParam;
			Paint(hdc);
			break;
		}
		case WM_PRINT:
		{
			HDC hdc = (HDC)wParam;
			Paint(hdc);
			break;
		}
    }
    return DefWindowProc( hWnd, Message, wParam, lParam );
}

// This function loads a file into an IStream.
void LoadPictureFile(LPCTSTR szFile)
{
	// open file
	HANDLE hFile = CreateFile(szFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if(hFile == INVALID_HANDLE_VALUE)
		return;

	// get file size
	DWORD dwFileSize = GetFileSize(hFile, NULL);

	LPVOID pvData = NULL;
	// alloc memory based on file size
	HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize);

	pvData = GlobalLock(hGlobal);

	DWORD dwBytesRead = 0;
	// read file and store in global memory
	ReadFile(hFile, pvData, dwFileSize, &dwBytesRead, NULL);
	GlobalUnlock(hGlobal);
	CloseHandle(hFile);

	LPSTREAM pstm = NULL;
	// create IStream* from global memory
	CreateStreamOnHGlobal(hGlobal, TRUE, &pstm);

	// Create IPicture from image file
	if (pPicture)
		pPicture->Release();
	OleLoadPicture(pstm, 0, FALSE, IID_IPicture, (LPVOID *)&pPicture);
	pstm->Release();
}

BOOL UPathFileExists(LPCTSTR pszPath)
{
	SetLastError(0);
	if(GetFileAttributes(pszPath) == INVALID_FILE_ATTRIBUTES)
	{
		return FALSE;
	}
	return TRUE;
}

LPTSTR UPathRemoveFileSpec(LPTSTR pszPath)
{
	SetLastError(0);
	if((GetFileAttributes(pszPath) & FILE_ATTRIBUTE_DIRECTORY) && !GetLastError())
	{
		return pszPath;//no changes necessary
	}
	//remove everything AFTER the last slash
	int i;
	for(i = lstrlen(pszPath)-1; i >= 0; i--)
	{
		if(pszPath[i] == _T('\\') || pszPath[i] == _T('/'))
		{
			//start from here
			break;
		}
	}
	if(i < 0)
	{
		return pszPath;
	}
	int slen = (int)lstrlen(pszPath);
	for(int j = i; j < slen; j++)
	{
		pszPath[j] = 0;
	}
	return pszPath;
}

typedef BOOL (*PANIMATEWINDOW)(HWND hwnd, DWORD dwTime, DWORD dwFlags);

void DoAnimate(HWND dlg, DWORD flag)
{
	HMODULE hlib = GetModuleHandle("USER32.DLL");
	PANIMATEWINDOW pAnimateWindow = (PANIMATEWINDOW)GetProcAddress(hlib, "AnimateWindow");
	if((LOBYTE(LOWORD(GetVersion())) >= 5) && (pAnimateWindow != NULL))
		(*pAnimateWindow)(dlg, 200, flag|AW_BLEND);
}

void DotNetNotInstalled()
{
	if((LOBYTE(LOWORD(GetVersion())) >= 6)) //Vista and 7 should have .NET installed
	{
		MessageBox(NULL, "The .NET Framework 3.0 or later must be installed in order to run this application.\n\nPlease select \"Microsoft .NET Framework\" on the next screen to install it. Once it's installed, please re-run this application.", ".NET Framework 3.0 Not Found", MB_OK | MB_ICONINFORMATION);
		ShellExecute(NULL, NULL, "optionalfeatures", NULL, NULL, SW_SHOWNORMAL);
		Sleep(1500);
		return;
	}
	if(MessageBox(NULL, "The .NET Framework 3.0 or later must be installed in order to run this application.\n\nClick OK to install .NET 3.5 from Microsoft's website. Once the .NET Framework is installed, please re-run this application.", ".NET Framework 3.0 Not Found", MB_OKCANCEL | MB_ICONINFORMATION) == IDOK)
	{
		ShellExecute(NULL, NULL, "http://www.microsoft.com/downloads/details.aspx?FamilyId=AB99342F-5D1A-413D-8319-81DA479AB0D7&displaylang=en", NULL, NULL, SW_SHOWNORMAL);
		Sleep(1500);
	}
}

void CheckDotNet()
{
	//check for presence of .NET Framework
	if(!UPathFileExists("Loader.ini"))
	{
		MessageBox(NULL, "Cannot find Loader.ini. The loader hasn't been properly configured.", "Error", MB_OK|MB_ICONSTOP);
		ExitProcess(0);
		return;
	}
	if(GetPrivateProfileInt("General", "RequireDotNet", 1, ".\\Loader.ini") == 1)
	{
		HKEY hkDotNet, hkDotNetVer;
		if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\NET Framework Setup\\NDP", 0, KEY_READ, &hkDotNet) != ERROR_SUCCESS)
		{
			DotNetNotInstalled();
			ExitProcess(0);
			return;
		}
		if(RegOpenKeyEx(hkDotNet, "v3.0", 0, KEY_READ, &hkDotNetVer) != ERROR_SUCCESS)
		{
			RegCloseKey(hkDotNet);
			DotNetNotInstalled();
			ExitProcess(0);
			return;
		}
		RegCloseKey(hkDotNet);
		RegCloseKey(hkDotNetVer);
	}
}

void LoadSplash(HINSTANCE hInstance)
{
	pPicture = NULL;
	if(GetPrivateProfileInt("Splash", "Enable", 1, ".\\Loader.ini") == 1)
	{
		GetPrivateProfileString("Splash", "File", "Splash.jpg", gstr, 260, ".\\Loader.ini");
		LoadPictureFile(gstr);
		if(pPicture != NULL)
		{
			// splash screen
			OLE_XSIZE_HIMETRIC width;
			OLE_YSIZE_HIMETRIC height;
			pPicture->get_Width( &width );
			pPicture->get_Height( &height );
			//Convert to device units
			int width2  = MulDiv(width,  GetDeviceCaps(GetDC(NULL), LOGPIXELSX), HIMETRIC_INCH);
			int height2 = MulDiv(height, GetDeviceCaps(GetDC(NULL), LOGPIXELSY), HIMETRIC_INCH);

			WNDCLASS    wc;

			SecureZeroMemory(&wc, sizeof(wc));

			wc.style         = CS_HREDRAW | CS_VREDRAW | CS_NOCLOSE;
			wc.lpfnWndProc   = (WNDPROC)MainWndProc;
			wc.cbClsExtra    = 0;
			wc.cbWndExtra    = 0;
			wc.hInstance     = hInstance;
			wc.hCursor       = NULL;
			wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
			wc.lpszMenuName  = NULL;
			wc.lpszClassName = "WND_Load";

			if(!RegisterClass(&wc))
			{
				ExitProcess(0);
				return;
			}
			hdlg = CreateWindowEx(
				WS_EX_TOOLWINDOW,
				"WND_Load",
				".NET Loader",
				WS_POPUP|WS_CLIPCHILDREN|WS_CLIPSIBLINGS,
				(GetSystemMetrics(SM_CXSCREEN)/2)-((width2/2)+1), (GetSystemMetrics(SM_CYSCREEN)/2)-((height2/2)+1),
				width2, height2,
				NULL,
				NULL,
				hInstance,
				NULL);
			if(hdlg == NULL)
			{
				ExitProcess(0);
				return;
			}

			DoAnimate(hdlg, AW_ACTIVATE);
			ShowWindow(hdlg, SW_SHOWNORMAL);
			BringWindowToTop(hdlg);
			UpdateWindow(hdlg);
		}
	}
}

void LoadMainEXE(LPTSTR lpCmdLine)
{
	STARTUPINFO si;

	SecureZeroMemory(&si, sizeof(STARTUPINFO));
	
	si.cb = sizeof(STARTUPINFO);

	GetPrivateProfileString("General", "Execute", "Main.exe", gstr, 260, ".\\Loader.ini");

	if(GetPrivateProfileInt("General", "RequireAdmin", 0, ".\\Loader.ini") == 1 && (LOBYTE(LOWORD(GetVersion())) >= 6))
	{
		//run elevated
		ShellExecute(NULL, "runas", gstr, NULL, NULL, SW_SHOWDEFAULT);
		Sleep(2000);
		if(hdlg != NULL)
		{
			DoAnimate(hdlg, AW_HIDE);
			DestroyWindow(hdlg);
		}
		if(pPicture != NULL)
			pPicture->Release();
		ExitProcess(0);
		return;
	}
	
	if(!CreateProcess(gstr, lpCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &p_i))
	{
		if(hdlg != NULL)
			DestroyWindow(hdlg);
		if(pPicture != NULL)
			pPicture->Release();
		MessageBox(NULL, "Loader Error: Could not load executable file mentioned in Loader.ini under [General], Execute=.", "Error", MB_OK|MB_ICONSTOP);
		ExitProcess(0);
		return;
	}
}

void WaitForProcess()
{
	FoundWindowHandle = false;

	// Smart wait - until window is available
	int i = 0;
	WaitForMainWindow(p_i.hProcess, p_i.dwProcessId);
	/*{
		PumpMessagesAndWaitForInputIdle(p_i.hProcess, 12000);
	}*/
	//this only waits in some cases:
	while(WaitForInputIdle(p_i.hProcess, 100) != 0 && i < 120)
	{
		i++;
		if(hdlg != NULL)
		{
			MSG msg;
			while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
			{
				if (!hdlg || !IsDialogMessage(hdlg, &msg))
				{
					TranslateMessage(&msg);
					DispatchMessage(&msg);
				}
			}
		}
		Sleep(100);
	}

	if(hdlg != NULL)
	{
		DoAnimate(hdlg, AW_HIDE);
		DestroyWindow(hdlg);
	}

	if(pPicture != NULL)
		pPicture->Release();
}

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	GetModuleFileName(NULL, gstr, 500);
	UPathRemoveFileSpec(gstr);
	if(lstrlen(gstr) > 0)
	{
		SetCurrentDirectory(gstr);
	}
	SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS);

	CheckDotNet();
	LoadSplash(hInstance);
	LoadMainEXE(lpCmdLine);

	Sleep(500);

	WaitForProcess();

	return 0;
}
